package org.xx.armory.commons;

import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalQueries;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.time.format.DateTimeFormatter.ISO_DATE;
import static java.time.format.DateTimeFormatter.ISO_DATE_TIME;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
import static java.util.Arrays.stream;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.split;
import static org.xx.armory.commons.Validators.rejectIfNegative;
import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 将字符串解析为其它类型的工具类。
 *
 * <p>和 {@link Converter}不同，此类型会尝试用可以识别的格式对字符串进行解析，并且在转化失败时返回默认值。</p>
 *
 * @see Converter
 */
public final class StringConverter {
    /**
     * 长日期格式。
     */
    private static final DateTimeFormatter L_DATE = DateTimeFormatter.ofPattern("yyyy-M-d'T'H:m:s");

    /**
     * 另一种长日期格式。
     */
    private static final DateTimeFormatter L_DATE2 = DateTimeFormatter.ofPattern("yyyy/M/d'T'H:m:s");
    /**
     * 长日期格式。
     */
    private static final DateTimeFormatter L_DATE3 = DateTimeFormatter.ofPattern("yyyy-M-d H:m:s");

    /**
     * 另一种长日期格式。
     */
    private static final DateTimeFormatter L_DATE4 = DateTimeFormatter.ofPattern("yyyy/M/d H:m:s");

    /**
     * 短日期格式。
     */
    private static final DateTimeFormatter S_DATE = DateTimeFormatter.ofPattern("yyyy/M/d");

    /**
     * 另一种短日期格式。
     */
    private static final DateTimeFormatter S_DATE2 = DateTimeFormatter.ofPattern("yyyy-M-d");

    /**
     * 另一种短日期格式。
     */
    private static final DateTimeFormatter S_DATE3 = DateTimeFormatter.ofPattern("yyyyMMdd");

    private static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-M");
    private static final DateTimeFormatter ALT_YEAR_MONTH_FORMATTER1 = DateTimeFormatter.ofPattern("yyyy/M");
    private static final DateTimeFormatter ALT_YEAR_MONTH_FORMATTER2 = DateTimeFormatter.ofPattern("yyyyMM");

    private static final NumberFormat FILE_SIZE_FORMAT = new DecimalFormat("#0.00");

    private StringConverter() {
        throw new AssertionError();
    }

    public static Byte parseByte(
            String value,
            Byte defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Byte.parseByte(value);
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static Short parseShort(
            String value,
            Short defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Short.parseShort(value);
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static Integer parseInteger(
            String value,
            Integer defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Integer.parseInt(value.trim());
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static Long parseLong(
            String value,
            Long defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Long.parseLong(value.trim());
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static Float parseFloat(
            String value,
            Float defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Float.parseFloat(value.trim());
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static Double parseDouble(
            String value,
            Double defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Double.parseDouble(value.trim());
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static BigDecimal parseBigDecimal(
            String value,
            BigDecimal defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return new BigDecimal(value.trim());
            } catch (NumberFormatException ignored) {
                return defaultValue;
            }
        }
    }

    public static byte[] parseBytes(
            String value,
            byte[] defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Base64.getDecoder().decode(value.trim());
            } catch (IllegalArgumentException ignored) {
                return defaultValue;
            }
        }
    }

    public static Boolean parseBoolean(
            String value,
            Boolean defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            value = value.toLowerCase().trim();
            if ("true".equals(value) || "yes".equals(value) || "on".equals(value) || "t".equals(value) || "y".equals(value) || "1".equals(value)) {
                return Boolean.TRUE;
            } else if ("false".equals(value) || "no".equals(value) || "off".equals(value) || "f".equals(value) || "n".equals(value) || "0".equals(value)) {
                return Boolean.FALSE;
            } else {
                return defaultValue;
            }
        }
    }

    /**
     * 将字符串解析为 {@link java.sql.Time} 类型。
     * <p>待解析的字符串格式要求是 {@literal hh:mm:ss} 。</p>
     *
     * @param value
     *         待解析的字符串。
     * @param defaultValue
     *         默认值。
     * @return 解析结果，如果解析失败则返回默认值。
     */
    public static java.sql.Time parseTime(
            String value,
            java.sql.Time defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return java.sql.Time.valueOf(value.trim());
            } catch (IllegalArgumentException ignored) {
                return defaultValue;
            }
        }
    }

    private static Date queryDateFromTemporal(
            TemporalAccessor temporal
    ) {
        final var zoneId = temporal.query(TemporalQueries.zoneId());
        final var offset = temporal.query(TemporalQueries.offset());
        final var date = temporal.query(TemporalQueries.localDate());
        final var time = temporal.query(TemporalQueries.localTime());

        final var dateTime = time != null ? date.atTime(time) : date.atStartOfDay();

        final OffsetDateTime offsetDateTime;
        if (zoneId != null) {
            offsetDateTime = dateTime.atZone(zoneId).toOffsetDateTime();
        } else if (offset != null) {
            offsetDateTime = dateTime.atOffset(offset);
        } else {
            offsetDateTime = dateTime.atZone(ZoneId.systemDefault()).toOffsetDateTime();
        }

        return Date.from(offsetDateTime.toInstant());
    }

    private static Date parseDateStrictly(
            String s,
            DateTimeFormatter... formatters
    ) {
        for (final var f : formatters) {
            try {
                return f.parse(s).query(StringConverter::queryDateFromTemporal);
            } catch (DateTimeParseException ignored) {
            }
        }

        return null;
    }

    private static LocalDate parseLocalDateStrictly(
            String s,
            DateTimeFormatter... formatters
    ) {
        for (final var f : formatters) {
            try {
                return f.parse(s).query(TemporalQueries.localDate());
            } catch (DateTimeParseException ignored) {
            }
        }

        return null;
    }

    /**
     * 将字符串解析为 {@link java.util.Date} 类型。
     * <p>可以接受短日期或者长日期格式，如果是长日期格式，那么<strong>不能</strong>包含时区。</p>
     *
     * @param value
     *         待解析的字符串。
     * @param defaultValue
     *         默认值。
     * @return 解析结果，如果解析失败则返回默认值。
     */
    public static Date parseDate(
            String value,
            Date defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var result = parseDateStrictly(value.trim(), L_DATE, L_DATE2, L_DATE3, L_DATE4, S_DATE, S_DATE2, S_DATE3);
            return result != null ? result : defaultValue;
        }
    }

    /**
     * 将字符串解析为 {@link java.util.Date} 类型。
     * <p>只接受ISO8601格式或者RFC822格式。</p>
     *
     * @param value
     *         待解析的字符串。
     * @param defaultValue
     *         默认值。
     * @return 解析结果，如果解析失败则返回默认值。
     */
    public static Date parseTimestamp(
            String value,
            Date defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var result = parseDateStrictly(value.trim(), ISO_DATE_TIME, DateTimeFormatter.RFC_1123_DATE_TIME);// ISO8601_FORMAT, ISO8601_FORMAT_2, RFC822_FORMAT);
            return result != null ? result : defaultValue;
        }
    }

    /**
     * 将字符串解析为 {@link java.util.Date} 类型。
     * <p>可以接受短日期或者长日期格式，可以包含时区，也不可以不包含时区。</p>
     *
     * @param value
     *         待解析的字符串。
     * @param defaultValue
     *         默认值。
     * @return 解析结果，如果解析失败则返回默认值。
     */
    public static Date parseTimestampWithLeniency(
            String value,
            Date defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var result = parseDateStrictly(value.trim(), ISO_DATE_TIME, RFC_1123_DATE_TIME, L_DATE, L_DATE2, L_DATE3, L_DATE4, S_DATE, S_DATE2, S_DATE3);
            return result != null ? result : defaultValue;
        }
    }

    public static LocalDate parseLocalDate(
            String value,
            LocalDate defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var result = parseLocalDateStrictly(value, ISO_DATE, S_DATE, S_DATE3);
            return result != null ? result : defaultValue;
        }
    }

    private static ByteBuffer parseByteBufferStrictly(
            String value,
            Base64.Decoder... decoders
    ) {
        for (final var d : decoders) {
            try {
                return ByteBuffer.wrap(d.decode(value));
            } catch (IllegalArgumentException ignored) {
            }
        }

        return null;
    }

    public static ByteBuffer parseByteBuffer(
            String value,
            ByteBuffer defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var result = parseByteBufferStrictly(value.trim(), Base64.getUrlDecoder(), Base64.getDecoder(), Base64.getMimeDecoder());
            return result != null ? result : defaultValue;
        }
    }

    private static YearMonth parseYearMonthStrictly(
            String value,
            DateTimeFormatter... formatters
    ) {
        for (final var f : formatters) {
            try {
                return YearMonth.parse(value, f);
            } catch (DateTimeParseException ignored) {
            }
        }

        return null;
    }

    public static YearMonth parseYearMonth(
            String value,
            YearMonth defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var result = parseYearMonthStrictly(value.trim(), YEAR_MONTH_FORMATTER, ALT_YEAR_MONTH_FORMATTER1, ALT_YEAR_MONTH_FORMATTER2);
            return result != null ? result : defaultValue;
        }
    }

    public static Path parsePath(
            String value,
            Path defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Paths.get(value);
            } catch (InvalidPathException ignored) {
                return defaultValue;
            }
        }
    }

    public static InetAddress parseInetAddress(
            String value,
            InetAddress defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return InetAddress.getByName(value.trim());
            } catch (UnknownHostException ignored) {
                return defaultValue;
            }
        }
    }

    public static Charset parseCharset(
            String value,
            Charset defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Charset.forName(value);
            } catch (UnsupportedCharsetException ignored) {
                return defaultValue;
            }
        }
    }

    public static Class<?> parseClass(
            String value,
            Class<?> defaultValue
    ) {
        if (isBlank(value)) {
            return defaultValue;
        } else {
            try {
                return Class.forName(value.trim());
            } catch (ClassNotFoundException ignored) {
                return defaultValue;
            }
        }
    }

    public static <T extends Enum<T> & IntegerEnum> T parseIntegerEnum(
            String value,
            T defaultValue,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var iv = parseInteger(value, 0);
            for (final var ev : clazz.getEnumConstants()) {
                if (ev.value() == iv) {
                    return ev;
                }
            }

            return defaultValue;
        }
    }

    public static <T extends Enum<T> & StringEnum> T parseStringEnum(
            String value,
            T defaultValue,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        if (isBlank(value)) {
            return defaultValue;
        } else {
            final var sv = value.trim();
            for (final var ev : clazz.getEnumConstants()) {
                if (Objects.equals(ev.stringValue(), sv)) {
                    return ev;
                }
            }

            return defaultValue;
        }
    }

    public static List<String> parseStrings(
            String value,
            Collection<String> defaultValue
    ) {
        if (isBlank(value)) {
            return new ArrayList<>(defaultValue);
        } else {
            return stream(split(value, ','))
                    .map(String::trim)
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toList());
        }
    }

    /**
     * 将字节数组格式化为十六进制格式的字符串。
     *
     * <p>
     * 字节数组中的每个元素被格式化为2位的十六进制字符，高位在前。
     * </p>
     * <p>
     * 示例：
     * </p>
     *
     * <pre>
     * <code>
     * formatHexString(null) == "";
     * formatHexString(new byte[0]) == "";
     * formatHexString(new byte[] { 0x25, 073, 0x99 }) == "257399"
     * </code>
     * </pre>
     *
     * @param b
     *         需格式化的字节数组
     * @return 采用十六进制格式表示的字符串，如果参数<code>b</code>的长度为0或者是<code>null</code>，那么返回空字符串。
     * @see java.lang.String#format
     */
    public static String formatHexString(
            byte[] b
    ) {
        if (b == null || b.length == 0) {
            return "";
        }

        final var sb = new StringBuilder();
        for (final var bb : b) {
            sb.append(String.format("%02x", bb));
        }

        return sb.toString();
    }

    public static byte[] parseHexString(
            String s
    ) {
        if (s == null || s.isBlank()) {
            return new byte[0];
        }

        final var buf = new StringBuilder(2);

        final var b0 = s.chars().filter(ch -> !Character.isWhitespace(ch))
                        .map(ch -> {
                            buf.append((char) ch);
                            if (buf.length() >= 2) {
                                final var b = Integer.parseInt(buf.toString(), 16);
                                buf.setLength(0);
                                return b;
                            } else {
                                return -1;
                            }
                        }).filter(b -> b >= 0).toArray();

        final var result = new byte[b0.length];
        for (var i = 0; i < b0.length; ++i) {
            result[i] = (byte) b0[i];
        }

        return result;
    }

    /**
     * 格式化文件大小。
     *
     * @param size
     *         文件大小。
     * @return 格式化后的文本。
     */
    public static String formatFileSize(
            long size
    ) {
        rejectIfNegative(size, "size");

        if (size == 0) {
            return "0 B";
        } else if (size < 1024L * 3) {
            return size + " B";
        } else if (size < 1024L * 1024 * 3) {
            return FILE_SIZE_FORMAT.format(size / 1024D) + " KBytes";
        } else if (size < 1024L * 1024 * 1024 * 3) {
            return FILE_SIZE_FORMAT.format(size / (1024D * 1024)) + " MBytes";
        } else {
            return FILE_SIZE_FORMAT.format(size / (1024D * 1024 * 1024)) + " GBytes";
        }
    }

    /**
     * 使用指定的分隔符将Stream连接为字符串。
     * <p>如果参数 {@code stream}是{@code null}则返回空字符串。</p>
     *
     * @param delimiter
     *         分隔符。
     * @param stream
     *         待连接的Stream。
     * @return 连接后的字符串。
     */
    public static String join(
            CharSequence delimiter,
            Stream<?> stream
    ) {
        if (stream == null) {
            return "";
        }

        return String.join(delimiter, stream.filter(Objects::nonNull).map(Object::toString).toArray(String[]::new));
    }
}
